home *** CD-ROM | disk | FTP | other *** search
/ Windows Expert / Windows Expert.iso / desktop / addmenu4.zip / SOURCE.ZIP / ADDMENU.C next >
C/C++ Source or Header  |  1992-07-08  |  38KB  |  1,247 lines

  1. /* AddMenu, v0.4.1
  2.  *
  3.  * This program is notably inefficient insofar as every change that is save is written to
  4.  * the profile file.  Granted, now that we all use disk caches that's not really a problem,
  5.  * but I admit that it isn't great programming technique.  I'm lazy.
  6.  *
  7.  * The program, AddMenu, is designed to let you add options to your system menus.  When
  8.  * running, it hooks into the system menus to add the specified options to all menus.  It
  9.  * also hooks into the keyboard and mouse to intercept requests for those options in order
  10.  * to execute those commands.
  11.  *
  12.  * Please appreciate that this is version 0.  This means that it is a pre-release release.
  13.  * In other words, I appreciate that there are problems with the code, but I don't have
  14.  * time to worry about it right now.  If you find bugs, please let me know, but don't
  15.  * expect a timely response.  But by all means, let me know what you think.
  16.  *
  17.  * There are many notable limitations/problems with the program as it currently exists:
  18.  *
  19.  *   1.    No control of the order of items on the menu.
  20.  *   2.    Program currently uses both WH_CALLWNDPROC and WH_GETMESSAGE hooks.  The first
  21.  *    adversely affects system performance.
  22.  *   3.    This program rather arbitrarily uses menu id's starting at 0xE120.  This is intended
  23.  *    to minimized conflicts with existing system menus (0xF000 and higher) and likely
  24.  *    ids used by other programs.  It is possible that these could conflict with other
  25.  *    programs.  By adding a line "First Id=n" to the profile, where "n" is some number,
  26.  *    can control what id should be used.  I didn't know how else to get around possible
  27.  *    conflicts.
  28.  *   4.    No help file yet.
  29.  *   5. And more, as of yet undiscovered, problems ...
  30.  *
  31.  * Program requires COMMDLG.DLL, which comes with Windows 3.1 (but can be distributed with
  32.  * the program).  Program also requires AMFILTER.DLL which installs the two filters and handles
  33.  * the menu events and posts the appropriate private messages to this program.
  34.  *
  35.  * This program is copywrite of Robert M. Ryan, 1992.  It is provided without warrantee
  36.  * of any sort.  This program is FreeWare, and can be used and distributed for
  37.  * non-commercial use without fee providing that:
  38.  *
  39.  *     a) it is not to be altered without my permission;
  40.  *    b) my name remains on the package at all times;
  41.  *    c) any programs which employ code taken from this program must credit me for
  42.  *       the appropriate routines; and
  43.  *    d) no fee is ever charged for the distribution of the program short of the cost
  44.  *       of disk media and shipping cost (if any).
  45.  *
  46.  * If you want to use it for commercial purposes or have any questions about these policies,
  47.  * do not hesitate to contact me.
  48.  *
  49.  * v0.3.0, Robert M. Ryan, 28 April 1992, first public release
  50.  * v0.3.1
  51.  *    - fix exit bug
  52.  *    - modify so combo box works like it's supposed to
  53.  * v0.3.2
  54.  *    - remove extraneous check of WM_MOVE (fixed by 0.3.1)
  55.  *    - extend "executables" to include "*.exe;*.com;*.pif;*.bat"
  56.  * v0.3.3, 9 May 1992
  57.  *    - Change filter to {"Programs","*.exe;*.com;*.pif;*.bat","All Files","*.*"}
  58.  * v0.3.4, 12 May 1992
  59.  *    - Change "All Files" to "All Files *.*"
  60.  * v0.3.5, 7 June 1992
  61.  *    - Fix bug in Browse. Initialize lpFilename to "\0".
  62.  * v0.3.6, 6 July 1992
  63.  *    - Extend WinExec() processing to change drive and directory first.
  64.  *    - Add error messages to WinExec() (see Run())
  65.  *    - Fix bug about change of current entry
  66.  * v0.4.1, 7 July 1992
  67.  *    - Simplified and debugged PM_UPDATE code to simply account for subtle
  68.  *      differences between CBN_SELCHANGE and CBN_EDITUPDATE.  Both don't
  69.  *      change visible contents of IDD_MENUNAME, but the first actually
  70.  *      doesn't update GetDlgItemText() while the second does.
  71.  *    - Second public release
  72.  *
  73.  * Rob Ryan
  74.  *    internet:   Robert_Ryan@brown.edu
  75.  *    bitnet:     ST802200@BROWNVM.BITNET
  76.  *    Compu$erve: 70324,227
  77.  */
  78.  
  79.  
  80. /* global defines */
  81.  
  82. #define PROGNAME    "AddMenu"
  83. #define VERSION        PROGNAME " v0.4.1"
  84. #define VERDATE        __DATE__
  85. #define PROGRAMMER    "Robert M. Ryan"
  86. #define STRING_MAX    256            /* COMMDLG requires at least 256*/
  87. #define IDM_FIRST    0xE120            /* default first id for additions to system menu */
  88. #define IDM_INCREMENT    0x10            /* increment between successive ids */
  89. #define HANDLE_MAX    200            /* how many parent handles can we enumerate? */
  90. #define BUF_SIZE    8000            /* size of buffer to hold ini stuff */
  91. #define WILDCARD    "*.exe;*.com;*.pif;*.bat" /* what wildcards for browse command, 0.3.2 */
  92. #define TICKTHRESH    1000            /* minimium GetTickCount() interval */
  93. enum SeparatorTypes {SEP_NONE = 0, SEP_LINE, SEP_BREAK};
  94.  
  95.  
  96. /****************************************************************************
  97.  * include files
  98.  ****************************************************************************/
  99.  
  100. #include <windows.h>
  101. #include <stdarg.h>
  102. #include <dos.h>
  103. #include <direct.h>
  104. #include <stdio.h>
  105. #include <stdlib.h>
  106. #include <string.h>
  107. #include <commdlg.h>
  108. #include <ctype.h>
  109.  
  110. #include "addmenu.h"
  111. #include "amfilter.h"
  112.  
  113.  
  114. /****************************************************************************
  115.  * type definitions
  116.  ****************************************************************************/
  117.  
  118. typedef struct tagMenuLst {        /* used for linked list of menu commands */
  119.     PSTR          szText;
  120.     PSTR          szCommand;
  121.     struct tagMenuLst *pNext;
  122. } MENULST;
  123.  
  124. typedef HWND FAR *LPHWND;
  125.  
  126.  
  127. /****************************************************************************
  128.  * global variables
  129.  ****************************************************************************/
  130.  
  131. LPSTR    lpClass        = PROGNAME;
  132. HANDLE    hInst;
  133. HWND    hwndMain;            /* window handle of main program */
  134. HWND    hwndDialog;            /* window handle of main dialog */
  135. HWND    hwndCombo;            /* window handle of combobox */
  136. FARPROC    lpitMain;            /* instance thunk of main dialog */
  137.  
  138. /* misc .ini variables */
  139.  
  140. LPSTR    lpHidden    = "Hidden";
  141. LPSTR    lpSeparator = "Separator";
  142. LPSTR    lpFirst        = "First Id";
  143. LPSTR    lpOptions   = "Options";
  144. LPSTR    lpProfile   = "addmenu.ini";
  145.  
  146. /* misc program status variables */
  147.  
  148. BOOL    bHidden;            /* is icon hidden? */
  149. WORD    nSeparator;            /* either SEP_NONE, SEP_LINE, or SEP_BREAK */
  150. WORD    wFirst;                /* what is the first id to be used */
  151. BOOL    bChanges;            /* are there any changes to save? */
  152. BOOL    bChangesMade;            /* have any changes been saved? */
  153.  
  154. /* Keep track of time that command was last executed.  This is here
  155.  * because Write (in Windows 3.1) apparently sends two commands.  It
  156.  * is the only application that I've seen that does it, but to prevent
  157.  * problems, I check these variables and make sure that TICKTHRES msec
  158.  * have taken place before issuing the same command.
  159.  */
  160. DWORD    dwLastCmdTicks;            /* when was the last PM_MENUOPTION (in msec) */
  161. WORD    wLastCmd;            /* and what was it */
  162.  
  163. /* the base for the linked list of menu options */
  164.  
  165. MENULST    mlFirst = { NULL, NULL, NULL };
  166.  
  167. /* variables used while editing dialog box contents */
  168.  
  169. char    szMenuName[STRING_MAX];
  170. LPSTR    lpMenuName = szMenuName;
  171. char    szPrevName[STRING_MAX];
  172. LPSTR    lpPrevName = szMenuName;
  173. char    szFilename[STRING_MAX];
  174. LPSTR    lpFilename = szFilename;
  175.  
  176. /* variables used in GetOpenFileName() from COMMDLG.H */
  177.  
  178. LPSTR    lpFilter = "Programs\0" WILDCARD "\0All Files (*.*)\0*.*\0\0";
  179. LPSTR    lpPick = "Browse";
  180. LPSTR    lpExe = "exe";
  181.  
  182. /* variables use in the enumeration of windows */
  183.  
  184. LPHWND    hwndArray;
  185. int    nCountArray;
  186. int     nCountWindow;
  187.  
  188.  
  189. /****************************************************************************
  190.  * function prototypes
  191.  ****************************************************************************/
  192.  
  193. void ReadIni(void);
  194. LONG FAR PASCAL WndProc(HWND hWnd, WORD wMsg, WORD wParam, LONG lParam);
  195. int  FAR PASCAL AboutDlg(HWND hDlg, WORD wMsg, WORD wParam, LONG lParam);
  196. int  FAR PASCAL MainDlg(HWND hDlg, WORD wMsg, WORD wParam, LONG lParam);
  197. LPSTR SpaceLess(LPSTR lpStr);
  198. BOOL Run(HWND hWnd, PSTR szFile, int nCmdShow);
  199. void ErrorBox(HWND hWnd, PSTR szFormat, ...);
  200. void Modal(HWND hWnd, LPSTR lpName, FARPROC lpFunc);
  201. void WritePrivateProfileInt(LPSTR lpAppName, LPSTR lpKeyName, int nInt, LPSTR lpFileName);
  202. int  FindExactInCB(HWND hDlg, int nIdd, LPSTR lpName);
  203. void EnableMenus(HWND hWnd);
  204. void PickFile(HWND hWnd);
  205. void ProcessWindows(void);
  206. BOOL FAR PASCAL EnumWindowsFunc(HWND hWnd, DWORD lParam);
  207. BOOL FAR PASCAL EnumChildrenFunc(HWND hWnd, DWORD lParam);
  208. void FixWindow(HWND hWnd);
  209.  
  210.  
  211. /****************************************************************************
  212.  * the code
  213.  ****************************************************************************/
  214.  
  215. int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
  216. {
  217.     WNDCLASS    wndclass;
  218.     MSG        msg;
  219.     LONG    lDlgBaseUnits = GetDialogBaseUnits();
  220.     int        nWidth, nHeight;
  221.     HWND    hWnd;
  222.  
  223.     /* if already running, make it visible if its not already, and give focus */
  224.  
  225.     if (hPrevInstance != NULL) {
  226.     hWnd = FindWindow(lpClass, lpClass);
  227.     ShowWindow(hWnd, SW_RESTORE);
  228.     SetFocus(hWnd);
  229.     return 0;
  230.     }
  231.  
  232.     /* read profile string parameters */
  233.     ReadIni();
  234.  
  235.     hInst      = hInstance;
  236.  
  237.     wndclass.style       = 0;
  238.     wndclass.lpfnWndProc   = WndProc;
  239.     wndclass.cbClsExtra       = 0;
  240.     wndclass.cbWndExtra       = 0;
  241.     wndclass.hInstance       = hInstance;
  242.     wndclass.hIcon       = LoadIcon(hInstance, MAKEINTRESOURCE(1));
  243.     wndclass.hCursor       = NULL;
  244.     wndclass.hbrBackground = COLOR_WINDOW + 1;
  245.     wndclass.lpszMenuName  = NULL;
  246.     wndclass.lpszClassName = lpClass;
  247.  
  248.     RegisterClass(&wndclass);
  249.  
  250.     /* Make Window big enough for child dialog (exactly) */
  251.  
  252.     nWidth  = (WINDOW_WIDTH * LOWORD(lDlgBaseUnits)) / 4 +
  253.                 GetSystemMetrics(SM_CXBORDER) * 2;
  254.     nHeight = (WINDOW_HEIGHT * HIWORD(lDlgBaseUnits)) / 8 +
  255.                 GetSystemMetrics(SM_CYBORDER) * 2 +
  256.                 GetSystemMetrics(SM_CYMENU) +
  257.                 GetSystemMetrics(SM_CYCAPTION);
  258.  
  259.     hwndMain = CreateWindow(lpClass,
  260.             lpClass,
  261.             WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU, /* WS_OVERLAPPEDWINDOW, */
  262.             CW_USEDEFAULT, 0,
  263.             nWidth, nHeight,
  264.             NULL, LoadMenu(hInstance, MAKEINTRESOURCE(1)),
  265.             hInstance, NULL);
  266.  
  267.     /* show it as appropriate */
  268.  
  269.     ShowWindow(hwndMain, nCmdShow);
  270.     SetFocus(hwndMain);
  271.  
  272.     while(GetMessage(&msg, NULL, 0, 0)) {
  273.     if (IsDialogMessage(hwndDialog, &msg))
  274.         continue;
  275.     TranslateMessage(&msg);
  276.     DispatchMessage(&msg);
  277.     }
  278.  
  279.     FreeProcInstance(lpitMain);
  280.  
  281.     return msg.wParam;
  282. } /* WinMain */
  283.  
  284.  
  285. /* ReadIni
  286.  *
  287.  * Read the contents from the private profile file.
  288.  */
  289. void ReadIni(void)
  290. {
  291.     HANDLE    hData;
  292.     LPSTR    pData;
  293.     LPSTR    pPtr;
  294.     char    szTemp[STRING_MAX];
  295.     MENULST    *pList = &mlFirst;
  296.  
  297.     bHidden    = GetPrivateProfileInt(lpClass, lpHidden, 0, lpProfile);
  298.     nSeparator = GetPrivateProfileInt(lpClass, lpSeparator, SEP_LINE, lpProfile);
  299.     wFirst     = GetPrivateProfileInt(lpClass, lpFirst, IDM_FIRST, lpProfile);
  300.  
  301.     hData = GlobalAlloc(GMEM_DISCARDABLE | GMEM_MOVEABLE, BUF_SIZE);
  302.     if (!hData) {
  303.     ErrorBox(NULL, "ReadIni: Unable to allocate memory");
  304.     return;
  305.     }
  306.  
  307.     pData = GlobalLock(hData);
  308.     if (!pData) {
  309.     ErrorBox(NULL, "ReadIni: Unable to access memory");
  310.     return;
  311.     }
  312.  
  313.     if (!GetPrivateProfileString(lpOptions, NULL, NULL, pData, BUF_SIZE, lpProfile)) {
  314.     GlobalUnlock(hData);
  315.     GlobalFree(hData);
  316.     return;
  317.     }
  318.  
  319.     pPtr = pData;
  320.     while (*pPtr) {
  321.     pList->pNext = malloc(sizeof(MENULST));
  322.     pList = pList->pNext;
  323.     if (!pList)
  324.         return;
  325.     pList->szText = malloc(lstrlen(pPtr) + 1);
  326.     lstrcpy(pList->szText, pPtr);
  327.     if (GetPrivateProfileString(lpOptions, pPtr, "", szTemp, STRING_MAX, lpProfile)) {
  328.         pList->szCommand = malloc(lstrlen(szTemp) + 1);
  329.         lstrcpy(pList->szCommand, szTemp);
  330.     } else
  331.         pList->szCommand = NULL;
  332.     pList->pNext = NULL;
  333.     pPtr += lstrlen(pPtr) + 1;
  334.     }
  335.     GlobalUnlock(hData);
  336.     GlobalFree(hData);
  337. } /* ReadIni */
  338.  
  339.  
  340. /* WndProc
  341.  *
  342.  *    The main window's callback function.
  343.  */
  344.  
  345. LONG FAR PASCAL WndProc(HWND hWnd, WORD wMsg, WORD wParam, LONG lParam)
  346. {
  347.     MENULST    *pList;
  348.     WORD    wIdd;
  349.     HMENU    hMenu;
  350.  
  351.     switch (wMsg) {
  352.     case WM_CREATE:            /* create child (modeless) dialog */
  353.         lpitMain = MakeProcInstance((FARPROC) MainDlg, hInst);
  354.         hwndDialog = CreateDialog(hInst, MAKEINTRESOURCE(2), hWnd, lpitMain);
  355.         SetHook(hWnd);
  356.         break;
  357.  
  358.     case WM_SETFOCUS:        /* If get focus, give it to dialog */
  359.         SetFocus(hwndDialog);
  360.         break;
  361.  
  362.     case WM_CLOSE:            /* send close message to child */
  363.         PostMessage(hwndDialog, wMsg, wParam, lParam);
  364.         break;
  365.  
  366.     case WM_INITMENU:        /* enable menu options as appropriate based upon the */
  367.         EnableMenus(hWnd);        /*    contents of the child dialog             */
  368.         break;
  369.  
  370.     case WM_SIZE:            /* if iconic, hide window if supposed to */
  371.         if (wParam == SIZEICONIC) {
  372.         SendMessage(hwndDialog, PM_QUERYSAVE, 0, 0L);
  373.         SendMessage(hwndDialog, PM_UPDATE, 0, 0L);
  374.         }
  375.         if ((wParam == SIZEICONIC) && bHidden) {
  376.         PostMessage(hwndMain, PM_HIDE, 0, 0L);
  377.         }
  378.         break;
  379.  
  380.     case PM_HIDE:            /* hide: called by WM_SIZE: need to use PostMessage() */
  381.         ShowWindow(hwndMain, SW_HIDE);
  382.         break;
  383.  
  384.     case PM_ADDTOMENU:        /* this is called by amfilter.dll for new menus */
  385.         hMenu = GetSystemMenu(wParam, 0);
  386.         if (GetMenuState(hMenu, wFirst, MF_BYCOMMAND) == (WORD) -1) {
  387.         pList = mlFirst.pNext;
  388.         wIdd  = wFirst;
  389.         if (pList && (nSeparator == SEP_LINE))
  390.             AppendMenu(hMenu, MF_SEPARATOR, 0, NULL);
  391.         while (pList) {        /* search linked list */
  392.             if (pList->szText)
  393.             if ((wIdd == wFirst) && (nSeparator == SEP_BREAK))
  394.                 AppendMenu(hMenu, MF_STRING | MF_ENABLED | MF_MENUBARBREAK, wIdd, pList->szText);
  395.             else
  396.                 AppendMenu(hMenu, MF_STRING | MF_ENABLED, wIdd, pList->szText);
  397.             wIdd += IDM_INCREMENT;
  398.             pList = pList->pNext;
  399.         }
  400.         }
  401.         break;
  402.  
  403.     case PM_MENUOPTION:             /* msg from dll saying that option selected from menu */
  404.         if ((wParam >= wFirst) && (wParam < SC_SIZE)) {
  405.         if (wParam == wLastCmd)        /* all because of Write in Windows 3.1! */
  406.             if ((GetTickCount() - dwLastCmdTicks) < TICKTHRESH)
  407.             break;
  408.         dwLastCmdTicks    = GetTickCount();
  409.         wLastCmd    = wParam;
  410.         pList        = mlFirst.pNext;
  411.         wIdd        = wFirst;
  412.         while(pList) {        /* search linked list for appropriate ID number */
  413.             if (wParam == wIdd) {
  414.             if (pList->szCommand) {
  415.                 Run(NULL, pList->szCommand, SW_SHOWNORMAL);
  416.             } else
  417.                 MessageBox(NULL, "This option has no command associated with it.",
  418.                     "System Menu Error", MB_ICONINFORMATION | MB_OK);
  419.             break;
  420.             }
  421.             wIdd += IDM_INCREMENT;    /* note: increment by 0x10 because system  */
  422.             pList = pList->pNext;    /*      menus apparently use lower 4 bits */
  423.         }
  424.         }
  425.         break;
  426.  
  427.     case WM_COMMAND:        /* Pass any menu commands to dialog */
  428.         switch (wParam) {
  429.         case IDM_NEW:
  430.         case IDM_SAVE:
  431.         case IDM_DELETE:
  432.         case IDM_PICK:
  433.         case IDM_EXIT:
  434.         case IDM_ABOUT:
  435.             SendMessage(hwndDialog, wMsg, wParam, lParam);
  436.             break;
  437.         default:
  438.             return(DefWindowProc(hWnd, wMsg, wParam, lParam));
  439.         } /* switch wParam */
  440.         break;
  441.  
  442.     case WM_DESTROY:            /* 0.3.1: somehow this got omitted */
  443.         PostQuitMessage(0);
  444.         break;
  445.  
  446.     /* 0.3.1: Since the combo box is in the child, but we're processing mouse input
  447.      * here, we have to perform the same sort of processing that DefDlgProc would.
  448.      */
  449.     case WM_LBUTTONDOWN:
  450.     case WM_NCLBUTTONDOWN:
  451.         if ((GetFocus() == hwndCombo) || (GetParent(GetFocus()) == hwndCombo))
  452.         SendMessage(hwndCombo, CB_SHOWDROPDOWN, FALSE, 0L);
  453.         return(DefWindowProc(hWnd, wMsg, wParam, lParam));
  454.  
  455.     default:
  456.         return(DefWindowProc(hWnd, wMsg, wParam, lParam));
  457.     }
  458.  
  459.     return FALSE;
  460. }
  461.  
  462.  
  463. /* AboutDlg
  464.  *
  465.  * This routine displays the about dialog box.
  466.  */
  467.  
  468. int FAR PASCAL AboutDlg(HWND hDlg, WORD wMsg, WORD wParam, LONG lParam)
  469. {
  470.     switch (wMsg) {
  471.     case WM_INITDIALOG:
  472.         SetDlgItemText(hDlg, IDD_TITLE, VERSION);
  473.         SetDlgItemText(hDlg, IDD_DATE,  VERDATE);
  474.         break;
  475.  
  476.     case WM_CLOSE:                /* did we manually close dlg? */
  477.         EndDialog(hDlg, FALSE);
  478.         break;
  479.  
  480.     case WM_COMMAND:
  481.         switch (wParam) {
  482.         case IDD_OK:            /* did we push ok? */
  483.             EndDialog(hDlg, FALSE);
  484.             break;
  485.         default:
  486.             return FALSE;
  487.         } /* switch wParam */
  488.         break;
  489.  
  490.     default:
  491.         return FALSE;
  492.     } /* switch wMsg */
  493.     return TRUE;
  494. } /* AboutDlg */
  495.  
  496.  
  497. /* MainDlg
  498.  *
  499.  * This modeless dialog box provides the lion's share of the processing in
  500.  * this program.  This is really the main callback function.
  501.  */
  502. BOOL FAR PASCAL MainDlg(HWND hDlg, WORD wMsg, WORD wParam, LONG lParam)
  503. {
  504.     int        i;
  505.     int        nIndex;
  506.     MENULST    *pList, *pPrev;
  507.  
  508.     switch (wMsg) {
  509.  
  510.     /* initialize the dialog */
  511.  
  512.     case WM_INITDIALOG:
  513.         /* Fill the combobox */
  514.  
  515.         pList = mlFirst.pNext;
  516.         while(pList) {
  517.         SendDlgItemMessage(hDlg, IDD_MENUNAME, CB_ADDSTRING, 0,
  518.                         (DWORD) (LPSTR) pList->szText);
  519.         pList = pList->pNext;
  520.         }
  521.  
  522.         CheckDlgButton(hDlg, IDD_HIDDEN, bHidden);
  523.         switch (nSeparator) {
  524.         case SEP_NONE:
  525.             CheckDlgButton(hDlg, IDD_SEPARATOR, 0);
  526.             CheckDlgButton(hDlg, IDD_NEWCOL, 0);
  527.             break;
  528.         case SEP_LINE:
  529.             CheckRadioButton(hDlg, IDD_SEPARATOR, IDD_NEWCOL, IDD_SEPARATOR);
  530.             break;
  531.         case SEP_BREAK:
  532.             CheckRadioButton(hDlg, IDD_SEPARATOR, IDD_NEWCOL, IDD_NEWCOL);
  533.             break;
  534.         }
  535.  
  536.         /* Set text limits */
  537.  
  538.         SendDlgItemMessage(hDlg, IDD_MENUNAME, CB_LIMITTEXT, STRING_MAX, 0L);
  539.         SendDlgItemMessage(hDlg, IDD_FILENAME, EM_LIMITTEXT, STRING_MAX, 0L);
  540.  
  541.         /* initialize routine's variables */
  542.  
  543.         bChanges     = FALSE;
  544.         bChangesMade = FALSE;
  545.         nIndex     = CB_ERR-1;
  546.         hwndCombo     = GetDlgItem(hDlg, IDD_MENUNAME);
  547.  
  548.         /* and prepare to draw screen */
  549.  
  550.         PostMessage(hDlg, PM_UPDATE, 0, 0L);
  551.  
  552.         break;
  553.  
  554.     /* if this gets focus, pass it to menu name field */
  555.  
  556.     case WM_SETFOCUS:
  557.         SetFocus(GetDlgItem(hDlg, IDD_MENUNAME));
  558.         break;
  559.  
  560.     case WM_CLOSE:            /* close: save changes? warn user */
  561.         SendMessage(hDlg, PM_QUERYSAVE, 0, 0L);
  562.  
  563.         if (IDYES != MessageBox(hDlg, "If you quit this program, you will not be "
  564.             "able to take advantage of the System Menu additions.  If you "
  565.             "wish to make use of the additions, please simply minimize "
  566.             "the program.  Do you REALLY want to quit?", "Quit?",
  567.             MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2))
  568.         break;
  569.         FreeHook();
  570.         ProcessWindows();
  571.  
  572.         DestroyWindow(hwndMain);
  573.         break;
  574.  
  575.     case PM_QUERYSAVE:        /* do you want to save? */
  576.         if (bChanges) {
  577.         if (MessageBox(hDlg, "Changes have been made."
  578.                 "  Do you want to save the changes?",
  579.                 "Save?",
  580.                 MB_YESNO | MB_ICONQUESTION) == IDYES)
  581.             SendMessage(hDlg, WM_COMMAND, IDM_SAVE, 0L);
  582.         bChanges = FALSE;
  583.         }
  584.         break;
  585.  
  586.     /* fill the dialog contents and enable controls based upon the
  587.      * currently selected menuname.  One of three conditions can
  588.      * hold:
  589.      *    1. The user typed something in the edit control;
  590.      *    2. The user selected some action (New, Delete, etc.)
  591.      *       which changed the contents of the edit control; or
  592.      *    3. The user selected something from the list box.
  593.      * If the 3rd is the case, the text of IDD_MENUNAME hasn't changed
  594.      * yet because of a peculiarity with CBN_SELCHANGE, so we have to
  595.      * use CB_GETLBTEXT in this case.  Otherwise, we can just use
  596.      * GetDlgItemText().
  597.      */
  598.     case PM_UPDATE:            /* fill in the contents of dialog */
  599.         if (wParam == CBN_SELCHANGE) {
  600.         i = SendDlgItemMessage(hDlg, IDD_MENUNAME, CB_GETCURSEL, 0, 0L);
  601.         SendDlgItemMessage(hDlg, IDD_MENUNAME, CB_GETLBTEXT, (WORD) i, (DWORD) lpMenuName);
  602.         } else if (!GetDlgItemText(hDlg, IDD_MENUNAME, lpMenuName, STRING_MAX))
  603.         i = CB_ERR;
  604.         else
  605.         i = FindExactInCB(hDlg, IDD_MENUNAME, lpMenuName);
  606.  
  607.         if (!lstrlen(lpPrevName))
  608.         nIndex = CB_ERR-1;
  609.  
  610.         lstrcpy(lpPrevName, lpMenuName);
  611.  
  612.         if (nIndex == i)
  613.         break;
  614.  
  615.         nIndex = i;
  616.  
  617.         if (nIndex == CB_ERR) {
  618.         SetDlgItemText(hDlg, IDD_FILENAME, "");
  619.         EnableWindow(GetDlgItem(hDlg, IDD_FILENAME),
  620.             GetWindowTextLength(GetDlgItem(hDlg, IDD_MENUNAME)));
  621.         EnableWindow(GetDlgItem(hDlg, IDD_FILEPROMPT),
  622.             GetWindowTextLength(GetDlgItem(hDlg, IDD_MENUNAME)));
  623.         } else {
  624.         EnableWindow(GetDlgItem(hDlg, IDD_FILENAME), TRUE);
  625.         EnableWindow(GetDlgItem(hDlg, IDD_FILEPROMPT), TRUE);
  626.  
  627.         if (GetPrivateProfileString(lpOptions, lpMenuName, "", lpFilename, STRING_MAX, lpProfile))
  628.             SetDlgItemText(hDlg, IDD_FILENAME, lpFilename);
  629.         else
  630.             SetDlgItemText(hDlg, IDD_FILENAME, "");
  631.         }
  632.  
  633.         bChanges = FALSE;
  634.         break;
  635.  
  636.     case WM_COMMAND:
  637.         switch (wParam) {
  638.  
  639.         /* Group name (folder) combo box message */
  640.  
  641.         case IDD_MENUNAME:
  642.             switch (HIWORD(lParam)) {
  643.             case CBN_SELCHANGE:
  644.             case CBN_EDITUPDATE:
  645.                 SendMessage(hDlg, PM_QUERYSAVE, 0, 0L);
  646.                 SendMessage(hDlg, PM_UPDATE, HIWORD(lParam), 0L);
  647.                 break;
  648.  
  649.             case CBN_ERRSPACE:
  650.                 ErrorBox(hDlg, "MainDlg:  CBN_ERRSPACE");
  651.                 break;
  652.  
  653.             default:
  654.                 return FALSE;
  655.             }
  656.             break;
  657.  
  658.         /* Any edit parameters change? */
  659.  
  660.         case IDD_FILENAME:
  661.             switch (HIWORD(lParam)) {
  662.             case EN_CHANGE:
  663.                 bChanges = TRUE;
  664.                 break;
  665.  
  666.             case EN_ERRSPACE:
  667.                 ErrorBox(hDlg, "ParamDlg:  EN_ERRSPACE");
  668.                 break;
  669.  
  670.             default:
  671.                 return FALSE;
  672.             }
  673.             break;
  674.  
  675.         /* Have check boxes been clicked? */
  676.  
  677.         case IDD_HIDDEN:        /* hidden check box used */
  678.             if (HIWORD(lParam) == BN_CLICKED) {
  679.             bHidden = !IsDlgButtonChecked(hDlg, wParam);
  680.             CheckDlgButton(hDlg, wParam, bHidden);
  681.             WritePrivateProfileInt(lpClass, lpHidden, bHidden, lpProfile);
  682.             } else
  683.             return FALSE;
  684.             break;
  685.  
  686.         case IDD_SEPARATOR:
  687.         case IDD_NEWCOL:
  688.             if (HIWORD(lParam) != BN_CLICKED)
  689.             return FALSE;
  690.             ProcessWindows();        /* before farting with nSeparator,       */
  691.                         /* get rid of existing separators if any */
  692.             if (IsDlgButtonChecked(hDlg, wParam)) {
  693.             CheckDlgButton(hDlg, wParam, 0);
  694.             nSeparator = SEP_NONE;
  695.             } else {
  696.             CheckRadioButton(hDlg, IDD_SEPARATOR, IDD_NEWCOL, wParam);
  697.             nSeparator = (wParam == IDD_SEPARATOR ? SEP_LINE : SEP_BREAK);
  698.             }
  699.             WritePrivateProfileInt(lpClass, lpSeparator, nSeparator, lpProfile);
  700.             break;
  701.  
  702.         case IDM_DELETE:        /* select delete from menu */
  703.             if (GetDlgItemText(hDlg, IDD_MENUNAME, lpMenuName, STRING_MAX)) {
  704.             if (MessageBox(hDlg, "Are you sure?", "Delete",
  705.                 MB_YESNO | MB_DEFBUTTON2 | MB_ICONQUESTION) == IDNO) {
  706.                 break;
  707.             }
  708.             } else
  709.             break;
  710.  
  711.             i = FindExactInCB(hDlg, IDD_MENUNAME, lpMenuName);
  712.             if (i != CB_ERR) {
  713.             SendDlgItemMessage(hDlg, IDD_MENUNAME, CB_SETCURSEL, (WORD) -1, 0L);
  714.             if (CB_ERR == SendDlgItemMessage(hDlg, IDD_MENUNAME,
  715.                         CB_DELETESTRING, i, 0L))
  716.                 ErrorBox(hDlg, "Delete: invalid index");
  717.             }
  718.  
  719.             SetDlgItemText(hDlg, IDD_MENUNAME, "");
  720.  
  721.             /* delete from profile file */
  722.  
  723.             WritePrivateProfileString(lpOptions, lpMenuName, NULL, lpProfile);
  724.             bChangesMade = TRUE;
  725.  
  726.             /* remove from all system menus */
  727.  
  728.             ProcessWindows();
  729.  
  730.             /* delete from linked list (keep node, remove szText) */
  731.  
  732.             pList = mlFirst.pNext;
  733.             pPrev = &mlFirst;
  734.             while (pList) {
  735.             if (!lstrcmp(lpMenuName, pList->szText)) {
  736.                 free(pList->szText);
  737.                 free(pList->szCommand);
  738.                 pPrev->pNext = pList->pNext;
  739.                 free(pList);
  740.                 pList = pPrev->pNext;
  741.             } else {
  742.                 pPrev = pList;
  743.                 pList = pList->pNext;
  744.             }
  745.             }
  746.  
  747.             /* and let's go back to what we were doing */
  748.  
  749.             SetFocus(hDlg);
  750.             PostMessage(hDlg, PM_UPDATE, 0, 0L);
  751.             break;
  752.  
  753.         case IDM_SAVE:            /* select save from menu */
  754.             if (!lpPrevName[0])
  755.             break;
  756.  
  757.             GetDlgItemText(hDlg, IDD_FILENAME, lpFilename, STRING_MAX);
  758.  
  759.             /* if the name isn't in listbox, add it and add it to the linked list */
  760.  
  761.             if (FindExactInCB(hDlg, IDD_MENUNAME, lpPrevName) == CB_ERR) {
  762.             SendDlgItemMessage(hDlg, IDD_MENUNAME, CB_ADDSTRING, 0, (DWORD) lpPrevName);
  763.  
  764.             /* add item to end of linked list */
  765.  
  766.             pList = &mlFirst;
  767.             while (pList->pNext)
  768.                 pList = pList->pNext;
  769.             pList->pNext     = malloc(sizeof(MENULST));
  770.             pList            = pList->pNext;
  771.             pList->pNext     = NULL;
  772.             pList->szText    = malloc(lstrlen(lpPrevName)+1);
  773.             pList->szCommand = malloc(lstrlen(lpFilename)+1);
  774.             lstrcpy(pList->szText,    lpPrevName);
  775.             lstrcpy(pList->szCommand, lpFilename);
  776.  
  777.             ProcessWindows();        /* add to system menus */
  778.             } else {        /* just update linked list entry */
  779.             pList = mlFirst.pNext;
  780.             while (pList) {
  781.                 if (!lstrcmp(lpPrevName, pList->szText)) {
  782.                 free(pList->szCommand);
  783.                 pList->szCommand = malloc(lstrlen(lpFilename)+1);
  784.                 lstrcpy(pList->szCommand, lpFilename); /* 0.3.6 */
  785.                 }
  786.                 pList = pList->pNext;
  787.             }
  788.             }
  789.  
  790.             WritePrivateProfileString(lpOptions, lpPrevName, lpFilename, lpProfile);
  791.             bChanges = FALSE;
  792.             bChangesMade = TRUE;
  793.  
  794.             /* let's go back to what we were doing */
  795.  
  796.             PostMessage(hDlg, PM_UPDATE, 0, 0L);
  797.             SetFocus(hDlg);
  798.             break;
  799.  
  800.         /* Close button pressed */
  801.  
  802.         case IDM_EXIT:
  803.             PostMessage(hDlg, WM_CLOSE, 0, 0L);
  804.             break;
  805.  
  806.         /* Pick button pressed */
  807.  
  808.         case IDM_PICK:
  809.             PickFile(hDlg);
  810.             break;
  811.  
  812.         /* New option selected */
  813.  
  814.         case IDM_NEW:
  815.             SendMessage(hDlg, WM_COMMAND, IDD_MENUNAME,
  816.                 MAKELONG(GetDlgItem(hDlg, IDD_MENUNAME), CBN_EDITUPDATE));
  817.             SetDlgItemText(hDlg, IDD_MENUNAME, "");
  818.             break;
  819.  
  820.         case IDM_ABOUT:
  821.             Modal(hDlg, (LPSTR) MAKEINTRESOURCE(1), (FARPROC) AboutDlg);
  822.             break;
  823.  
  824.         default:
  825.             return FALSE;
  826.         } /* switch wParam */
  827.         break;
  828.  
  829.     default:
  830.         return FALSE;
  831.     } /* switch wMsg */
  832.     return TRUE;
  833. } /* MainDlg */
  834.  
  835.  
  836. /* SpaceLess
  837.  *
  838.  * Remove leading spaces and delete everything after first subsequent space
  839.  */
  840. LPSTR SpaceLess(LPSTR lpStr)
  841. {
  842.     LPSTR lpPtr = lpStr;
  843.     int      i;
  844.  
  845.     while(lpPtr[0] == ' ')            /* Remove leading spaces */
  846.     lpPtr++;
  847.  
  848.     for (i = 0; i < lstrlen(lpPtr); i++)    /* Remove anything after */
  849.     if (lpPtr[i] == ' ')            /*    any spaces       */
  850.         lpPtr[i] = '\0';
  851.  
  852.     return lpPtr;
  853. } /* SpaceLess */
  854.  
  855.  
  856. /* Run
  857.  *
  858.  * The WinExec() function has several notable limitations.  Notably, unlike
  859.  * the program manager's "Run" command, it does not change disk or drive.
  860.  * This addresses these limitations, simulating the "Run" command.
  861.  *
  862.  * v0.3.6
  863.  */
  864. BOOL Run(HWND hWnd, PSTR szFile, int nCmdShow)
  865. {
  866.     WORD     wRc;
  867.     OFSTRUCT of;
  868.     char     szFileName[STRING_MAX+1];
  869.     int         i;
  870.     int      nDrives;
  871.     BOOL     bExtension = FALSE;
  872.  
  873.     /* change to windows disk and directory (0.3.6) */
  874.  
  875.     GetWindowsDirectory(szFileName, STRING_MAX);
  876.     if (szFileName[1] == ':')
  877.     _dos_setdrive(toupper(szFileName[0]) - 'A' + 1, &nDrives);
  878.     chdir(szFileName);
  879.  
  880.     strcpy(szFileName, szFile);        /* get file name  */
  881.  
  882.     /* add extension .exe if none */
  883.  
  884.     SpaceLess(szFileName);        /* remove options */
  885.     for (i = strlen(szFileName)-1; i >= 0; i--) {
  886.     if (szFileName[i] == '.') {
  887.         bExtension = TRUE;
  888.         break;
  889.     }
  890.     if (szFileName[i] == '\\')
  891.         break;
  892.     }
  893.     if (!bExtension)
  894.     strcat(szFileName, ".exe");
  895.  
  896.     /* see if file exists and get full path in process */
  897.  
  898.     if (OpenFile(SpaceLess(szFileName), &of, OF_EXIST) == -1) {
  899.     ErrorBox(NULL, "The file \"%s\" was not found.", of.szPathName);
  900.     return FALSE;
  901.     }
  902.  
  903.     /* copy the filename into lpFileName and set of.szPathName to be just path */
  904.  
  905.     lstrcpy(szFileName, of.szPathName);
  906.     for (i = lstrlen(of.szPathName)-1; i >= 0; i--)
  907.     if (of.szPathName[i] == '\\') {
  908.         of.szPathName[i] = '\0';
  909.         break;
  910.     }
  911.  
  912.     /* change to that directory 0.3.6 */
  913.  
  914.     if (of.szPathName[1] == ':')
  915.     _dos_setdrive(toupper(of.szPathName[0]) - 'A' + 1, &nDrives);
  916.     chdir(of.szPathName);
  917.  
  918.     /* add any parameters to our new file name */
  919.  
  920.     for (i = 0; i < lstrlen(szFile); i++)    /* copy parameters */
  921.     if ((szFile[i] == ' ') || (szFile[i] == '/')) {
  922.         strcat(szFileName, szFile + i);
  923.         break;
  924.     }
  925.  
  926.     wRc = WinExec(szFileName, nCmdShow);
  927.  
  928.     if (wRc > 32)
  929.     return TRUE;
  930.  
  931.     switch (wRc) {
  932.         case  0:
  933.         ErrorBox(hWnd, "System was out of memory, executable file was corrupt, or relocations were invalid.");
  934.         break;
  935.     case  2:    /* this should be caught by above processing */
  936.         ErrorBox(hWnd, "File was not found. (%s)", szFile);
  937.         break;
  938.     case  3:    /* this should be caught by above processing */
  939.         ErrorBox(hWnd, "Path was not found. (%s)", szFile);
  940.         break;
  941.     case  5:
  942.         ErrorBox(hWnd, "Attempt was made to dynamically link to a task, or there was a sharing or network-protection error.");
  943.             break;
  944.         case  6:
  945.         ErrorBox(hWnd, "Library required separate data segments for each task.");
  946.         break;
  947.     case  8:
  948.         ErrorBox(hWnd, "There was insufficient memory to start the application. (%s)", szFile);
  949.             break;
  950.         case  10:
  951.             ErrorBox(hWnd, "Windows version was incorrect.");
  952.         break;
  953.         case  11:
  954.             ErrorBox(hWnd, "Executable file was invalid. Either it was not a Windows application or there was an error in the .EXE image. (%s)", szFile);
  955.             break;
  956.         case  12:
  957.         ErrorBox(hWnd, "Application was designed for a different operating system.");
  958.         break;
  959.         case  13:
  960.         ErrorBox(hWnd, "Application was designed for MS-DOS 4.0. (%s)", szFile);
  961.             break;
  962.         case  14:
  963.             ErrorBox(hWnd, "Type of executable file was unknown. (%s)", szFile);
  964.         break;
  965.     case  15:
  966.         ErrorBox(hWnd, "Attempt was made to load a real-mode application (developed for an earlier version of Windows).");
  967.         break;
  968.         case  16:
  969.             ErrorBox(hWnd, "Attempt was made to load a second instance of an executable file containing multiple data segments that were not marked read-only.");
  970.             break;
  971.     case  19:
  972.             ErrorBox(hWnd, "Attempt was made to load a compressed executable file. The file must be decompressed before it can be loaded.");
  973.             break;
  974.         case  20:
  975.             ErrorBox(hWnd, "Dynamic-link library (DLL) file was invalid. One of the DLLs required to run this application was corrupt.");
  976.         break;
  977.     case  21:
  978.             ErrorBox(hWnd, "Application requires Microsoft Windows 32-bit extensions.");
  979.         break;
  980.     default:
  981.         ErrorBox(hWnd, "Unknown error (%d)", wRc);
  982.         break;
  983.     }
  984.  
  985.     return FALSE;
  986. } /* Run */
  987.  
  988.  
  989. /* ErrorBox
  990.  *
  991.  * Print an error box message.    Accepts a variable number of arguments
  992.  * according to the ANSI standard.  The szFormat string is a simple format
  993.  * string like used with printf, permitting the use of "%d" (int), "%ld"
  994.  * (long int), "%f" (double), "%s" (char *), and "%Fs" (LPSTR).
  995.  */
  996. void ErrorBox(HWND hWnd, PSTR szFormat, ...)
  997. {
  998.     va_list args;
  999.     char    szString[STRING_MAX];
  1000.  
  1001.     va_start(args, szFormat);
  1002.  
  1003.     vsprintf(szString, szFormat, args);
  1004.  
  1005.     MessageBox(hWnd, szString, "Error", MB_ICONEXCLAMATION | MB_OK);
  1006.  
  1007.     return;
  1008. } /* ErrorBox */
  1009.  
  1010.  
  1011. /* Modal
  1012.  *    This routine is the constructor for a modal dialog box.     Note that
  1013.  *    with this definition, merely creating a item of type "ModalDialog"
  1014.  *    will create a thunk instance and create the box.  The thunk is freed
  1015.  *    in when done and the focus is returned to what is was on entry.
  1016.  *    Note that there is no destructor because the instance is freed in
  1017.  *    this routine (this is so the focus window could be recorded and
  1018.  *    restored upon termination).
  1019.  */
  1020. void Modal(HWND hWnd, LPSTR lpName, FARPROC lpFunc)
  1021. {
  1022.     FARPROC lpitDialog;
  1023.     HWND hWndFocus = GetFocus();
  1024.  
  1025.     if (!(lpitDialog = MakeProcInstance(lpFunc, hInst)))
  1026.     ErrorBox(hWnd, "Modal constructor:  MakeProcInstance failure");
  1027.     if (DialogBox(hInst, lpName, hWnd, lpitDialog) == -1)
  1028.     ErrorBox(hWnd, "Modal constructor:  Unable to create dialog");
  1029.     FreeProcInstance(lpitDialog);
  1030.  
  1031.     SetFocus(hWndFocus);
  1032. } /* Modal */
  1033.  
  1034.  
  1035. /* WritePrivateProfileInt
  1036.  *
  1037.  * While the system provides both GetProfileString and GetProfileInt,
  1038.  * it only provide WriteProfileString.  This is the logical partner.
  1039.  */
  1040. void WritePrivateProfileInt(LPSTR lpAppName, LPSTR lpKeyName, int nInt, LPSTR lpFileName)
  1041. {
  1042.     static char szNumber[80];
  1043.  
  1044.     itoa(nInt, szNumber, 10);
  1045.     WritePrivateProfileString(lpAppName, lpKeyName, szNumber, lpFileName);
  1046.  
  1047. } /* WritePrivateProfileInt */
  1048.  
  1049.  
  1050. /* FindExactinCB
  1051.  *
  1052.  * The command to look up string in Combobox isn't too picky.  Let's find
  1053.  * the one which precisely matches the string we're looking for.
  1054.  */
  1055. int FindExactInCB(HWND hDlg, int nIdd, LPSTR lpName)
  1056. {
  1057.     int   i;
  1058.     char  szString[STRING_MAX];
  1059.     LPSTR lpString = szString;
  1060.  
  1061.     i = SendDlgItemMessage(hDlg, nIdd, CB_FINDSTRING, (WORD) -1, (DWORD) lpName);
  1062.     if (i != CB_ERR)
  1063.     SendDlgItemMessage(hDlg, nIdd, CB_GETLBTEXT, i, (DWORD) lpString);
  1064.  
  1065.     if (lstrcmp(lpString, lpName))
  1066.     return CB_ERR;
  1067.  
  1068.     return i;
  1069. } /* FindStringInCB */
  1070.  
  1071.  
  1072. void EnableMenus(HWND hWnd)
  1073. {
  1074.     HMENU hMenu;
  1075.     int      i;
  1076.  
  1077.     hMenu = GetMenu(hWnd);
  1078.  
  1079.     i = GetWindowTextLength(GetDlgItem(hwndDialog, IDD_MENUNAME));
  1080.  
  1081.     if (i) {
  1082.     EnableMenuItem(hMenu, IDM_SAVE,      MF_BYCOMMAND | MF_ENABLED);
  1083.     EnableMenuItem(hMenu, IDM_DELETE, MF_BYCOMMAND | MF_ENABLED);
  1084.     EnableMenuItem(hMenu, IDM_PICK,      MF_BYCOMMAND | MF_ENABLED);
  1085.     EnableMenuItem(hMenu, IDM_NEW,      MF_BYCOMMAND | MF_ENABLED);
  1086.     } else {
  1087.     EnableMenuItem(hMenu, IDM_SAVE,      MF_BYCOMMAND | MF_GRAYED);
  1088.     EnableMenuItem(hMenu, IDM_DELETE, MF_BYCOMMAND | MF_GRAYED);
  1089.     EnableMenuItem(hMenu, IDM_PICK,      MF_BYCOMMAND | MF_GRAYED);
  1090.     EnableMenuItem(hMenu, IDM_NEW,      MF_BYCOMMAND | MF_GRAYED);
  1091.     }
  1092.  
  1093.     /* this program can be neither maximized nor resized */
  1094.  
  1095.     hMenu = GetSystemMenu(hWnd, 0);
  1096.     EnableMenuItem(hMenu, SC_SIZE,     MF_BYCOMMAND | MF_GRAYED);
  1097.     EnableMenuItem(hMenu, SC_MAXIMIZE, MF_BYCOMMAND | MF_GRAYED);
  1098.  
  1099.     return;
  1100. } /* EnableMenus */
  1101.  
  1102.  
  1103. void PickFile(HWND hWnd)
  1104. {
  1105.     OPENFILENAME ofn;
  1106.  
  1107.     lpFilename[0] = '\0';
  1108.  
  1109.     ofn.lStructSize        = sizeof(OPENFILENAME);
  1110.     ofn.hwndOwner        = hWnd;
  1111.     ofn.hInstance        = hInst;
  1112.     ofn.lpstrFilter        = lpFilter;
  1113.     ofn.lpstrCustomFilter    = NULL;
  1114.     ofn.nMaxCustFilter        = 0;
  1115.     ofn.nFilterIndex        = 1;
  1116.     ofn.lpstrFile        = lpFilename;
  1117.     ofn.nMaxFile        = STRING_MAX;
  1118.     ofn.lpstrFileTitle        = NULL;
  1119.     ofn.nMaxFileTitle        = 0;
  1120.     ofn.lpstrInitialDir        = NULL;
  1121.     ofn.lpstrTitle        = lpPick;
  1122.     ofn.Flags            = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
  1123.     ofn.nFileOffset        = 0;
  1124.     ofn.nFileExtension        = 0;
  1125.     ofn.lpstrDefExt        = lpExe;
  1126.     ofn.lCustData        = 0;
  1127.     ofn.lpfnHook        = NULL;
  1128.     ofn.lpTemplateName        = NULL;
  1129.  
  1130.     if (GetOpenFileName(&ofn))
  1131.     SetDlgItemText(hWnd, IDD_FILENAME, lpFilename);
  1132. } /* PickFile */
  1133.  
  1134.  
  1135. void ProcessWindows(void)
  1136. {
  1137.     FARPROC    lpit = MakeProcInstance(EnumWindowsFunc, hInst);
  1138.     HANDLE    hData;
  1139.     int        i;
  1140.  
  1141.     if (!lpit) {
  1142.     ErrorBox(hwndMain, "ProcessWindows: Error making instance thunk (1).");
  1143.     return;
  1144.     }
  1145.     hData     = GlobalAlloc(GMEM_MOVEABLE, sizeof(HWND) * HANDLE_MAX);
  1146.     if (!hData) {
  1147.     ErrorBox(hwndMain, "ProcessWindows: Error creating data buffer.");
  1148.     return;
  1149.     }
  1150.     hwndArray = (LPHWND) GlobalLock(hData);
  1151.     if (!hwndArray) {
  1152.     ErrorBox(hwndMain, "ProcessWindows: Error accessing data buffer.");
  1153.     return;
  1154.     }
  1155.  
  1156.     nCountArray = 0;
  1157.     EnumWindows(lpit, 0L);
  1158.     FreeProcInstance(lpit);
  1159.  
  1160.     lpit = MakeProcInstance(EnumChildrenFunc, hInst);
  1161.     if (!lpit) {
  1162.     ErrorBox(hwndMain, "ProcessWindows: Error making instance thunk (2).");
  1163.     return;
  1164.     }
  1165.  
  1166.     nCountWindow = 0;
  1167.  
  1168.     for (i = 0; i < nCountArray; i++)
  1169.     EnumChildWindows(hwndArray[i], lpit, 0L);
  1170.  
  1171.     FreeProcInstance(lpit);
  1172.  
  1173. #if defined(DEBUG)
  1174.     ErrorBox(hwndMain, "There were %d parents and %d children.", nCountArray, nCountWindow);
  1175. #endif
  1176.  
  1177.     GlobalUnlock(hData);
  1178.     GlobalFree(hData);
  1179.  
  1180.     return;
  1181. } /* ProcessWindows */
  1182.  
  1183.  
  1184. BOOL FAR PASCAL EnumWindowsFunc(HWND hWnd, DWORD lParam)
  1185. {
  1186.     if (nCountArray >= HANDLE_MAX) {
  1187.     MessageBox(hwndMain, "Too many parents!", "Error", MB_ICONSTOP | MB_OK);
  1188.     return 0;
  1189.     }
  1190.  
  1191.     if (hWnd) {
  1192.     hwndArray[nCountArray] = hWnd;
  1193.     nCountArray++;
  1194.     }
  1195.  
  1196.     FixWindow(hWnd);
  1197.  
  1198.     return TRUE;
  1199. } /* EnumWindowsFunc */
  1200.  
  1201.  
  1202. BOOL FAR PASCAL EnumChildrenFunc(HWND hWnd, DWORD lParam)
  1203. {
  1204.     nCountWindow++;
  1205.     FixWindow(hWnd);
  1206.  
  1207.     return TRUE;
  1208. } /* EnumTaskWindows */
  1209.  
  1210.  
  1211. void FixWindow(HWND hWnd)
  1212. {
  1213.     HMENU   hMenu;
  1214.     MENULST *pList;
  1215.     WORD    wIdd;
  1216.     int        i;
  1217.  
  1218.     if (!hWnd) {
  1219.     return;                /* if no window, quit */
  1220.     }
  1221.     if (!(GetWindowLong(hWnd, GWL_STYLE) & WS_SYSMENU)) {
  1222.     return;                /* if no system menu in window, quit */
  1223.     }
  1224.  
  1225.     hMenu = GetSystemMenu(hWnd, 0);
  1226.     pList = mlFirst.pNext;
  1227.     wIdd  = wFirst;
  1228.  
  1229.     /* We're deleting all of them, so then do it.  Note that we also want to remove the
  1230.      * separator too, so this also removes the menu item before the first one found.
  1231.      * While this logic sound confusing, it is necessary to make sure that we haven't
  1232.      * deleted the first menu item.
  1233.      */
  1234.     if ((GetMenuState(hMenu, wIdd, MF_BYCOMMAND) != (WORD) -1) && (nSeparator == SEP_LINE)) {
  1235.     for (i = 1; i < GetMenuItemCount(hMenu); i++)
  1236.         if (GetMenuItemID(hMenu, i) == wIdd)
  1237.         DeleteMenu(hMenu, i-1, MF_BYPOSITION);
  1238.     }
  1239.     while (pList) {
  1240.     if (GetMenuState(hMenu, wIdd, MF_BYCOMMAND) != (WORD) -1)
  1241.         DeleteMenu(hMenu, wIdd, MF_BYCOMMAND);
  1242.     pList = pList->pNext;
  1243.     wIdd += IDM_INCREMENT;
  1244.     }
  1245.     return;
  1246. } /* FixWindow */
  1247.